home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-04-29 | 37.8 KB | 1,104 lines | [TEXT/CWIE] |
- //================================================================================
- // Greg Anderson
- // db+
- //
- // Abstract base class for DataBase document
- // 17 May 1994
- // 31 Dec 1994
- //================================================================================
-
- #include "DatabaseDocument.h"
-
- #include "GroupControlObject.h"
- #include "AbstractRecord.h"
- #include "DBElement.h"
- #include "DBProperty.h"
- #include "DataRecord.h"
- #include "Transaction.h"
- #include "UniqueID.h"
-
- #include "Exceptions.h"
-
- #define kFreeRecordListIndex 0
- #define kLastDataBlockFreeList (kNumberFreeLists - 1)
-
- //
- // For memcpy
- // For CopyMemory
- //
- #include "AbstractData.h"
-
- //--------------------------------------------------------------------------------
- // TDocumentFileInformation::AssignInitialID
- //--------------------------------------------------------------------------------
- void TDocumentFileInformation::AssignInitialID()
- {
- fDocumentID = GenerateUniqueID();
- }
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::~TDatabaseDocument
- //--------------------------------------------------------------------------------
- TDatabaseDocument::~TDatabaseDocument()
- {
- //
- // Save changes back to disk before deleting ourselves
- //
- // Shouldn't do this in the destructor; we should
- // make a virtual 'Dispose' method, and keep the
- // destructor protected.
- //
- this->FlushChangesToDisk();
-
- //
- // The database document owns the backing store object,
- // and deletes it at destructor time.
- //
- delete fBackingStore;
- fBackingStore = nil;
- } // TDatabaseDocument::~TDatabaseDocument
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::ObjectsKeySpace
- //--------------------------------------------------------------------------------
- Int64 TDatabaseDocument::ObjectsKeySpace() const
- {
- return fDocumentInformation.fDocumentID;
- } // TDatabaseDocument::ObjectsKeySpace
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::ReadRecordRange
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::ReadRecordRange(void* bufferStart, long byteOffsetToRecord, long numberOfBytes, TAbstractBackingStore* backingStoreToUse /*= nil*/)
- {
- REQUIREVALIDPOINTER(bufferStart);
- if(backingStoreToUse == nil)
- backingStoreToUse = fBackingStore;
-
- if(backingStoreToUse != nil)
- backingStoreToUse->Read(bufferStart, byteOffsetToRecord + this->fDocumentInformation.fStartOfRecordData, numberOfBytes);
- } // TDatabaseDocument::ReadRecordRange
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::WriteRecordRange
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::WriteRecordRange(void* bufferStart, long byteOffsetToRecord, long numberOfBytes, TAbstractBackingStore* backingStoreToUse /*= nil*/)
- {
- REQUIREVALIDPOINTER(bufferStart);
- if(backingStoreToUse == nil)
- backingStoreToUse = fBackingStore;
-
- if(backingStoreToUse != nil)
- backingStoreToUse->Write(bufferStart, byteOffsetToRecord + this->fDocumentInformation.fStartOfRecordData, numberOfBytes);
- } // TDatabaseDocument::WriteRecordRange
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::GetFirstFreeIndex
- //--------------------------------------------------------------------------------
- long TDatabaseDocument::GetFirstFreeIndex(long whichFreeList) const
- {
- Require((whichFreeList >= 0) && (whichFreeList < kNumberFreeLists));
- return fFreeList[whichFreeList];
- } // TDatabaseDocument::GetFirstFreeIndex
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::SetFreeIndex
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::SetFreeIndex(long whichFreeList, long firstFree)
- {
- Require((whichFreeList >= 0) && (whichFreeList < kNumberFreeLists));
- fFreeList[whichFreeList] = firstFree;
- } // TDatabaseDocument::SetFreeIndex
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::GetRecordCursor
- //--------------------------------------------------------------------------------
- AConst<TAbstractRecord> TDatabaseDocument::GetRecordCursor(long recordIndex) const
- {
- return AConst<TAbstractRecord>(this->GetRecord(recordIndex));
- } // TDatabaseDocument::GetRecordCursor
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::GetRecord
- //--------------------------------------------------------------------------------
- TAbstractRecord* TDatabaseDocument::GetRecord(long recordIndex) const
- {
- TAbstractRecord* cursor = nil;
-
- if(recordIndex != kNilIndex)
- {
- TGroupControlObject* group = this->GetGroupControlObject(recordIndex);
- cursor = group->GetRecord(recordIndex);
- }
-
- return cursor;
- } // TDatabaseDocument::GetRecord
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::FlushChangesToDisk
- //
- // Write everything in the database back to disk
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::FlushChangesToDisk()
- {
- //
- // Write our document information back to disk
- //
- this->WriteDocumentInformation();
-
- //
- // For every cached group control object we have
- // in memory, call its flush changes method.
- //
- if(fRecordGroupList != nil)
- for(long i=0; i<fNumberOfGroups; ++i)
- if(fRecordGroupList[i] != nil)
- fRecordGroupList[i]->FlushChangesToDisk();
- }
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::SetBackingStore
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::SetBackingStore(TAbstractBackingStore* backingStore)
- {
- TAbstractBackingStore* previousBackingStore = fBackingStore;
-
- //
- // If we already have a backing store, make sure that
- // the file it is attached to is saved before the backing
- // store object is deleted
- //
- this->FlushChangesToDisk();
-
- //
- // Set the new backing store object
- //
- fBackingStore = backingStore;
-
- //
- // Begin by writing out the document information into the new
- // backing store object.
- //
- this->WriteDocumentInformation();
-
- //
- // There may be chunks of the database still on disk in
- // the previous backing store object. We must load them into
- // memory and save them back to the new backing store object.
- //
- if(fRecordGroupList != nil)
- {
- for(long i=0; i<fNumberOfGroups; ++i)
- {
- //
- // If the record group is not in memory, then we
- // must read it in from the previous backing store
- // object.
- //
- if((fRecordGroupList[i] == nil) && (previousBackingStore != nil))
- {
- TGroupControlObject* group = CreateGroupControlObject(i * kRecordsPerGroup);
- ASSERT(group == fRecordGroupList[i]);
- group->ReadRecordGroupFromDisk(previousBackingStore);
- }
- //
- // If we have the record group in memory, then write
- // it out to the new backing store object.
- //
- if(fRecordGroupList[i] != nil)
- {
- fRecordGroupList[i]->RecordGroupHasChanged();
- fRecordGroupList[i]->FlushChangesToDisk();
- }
- }
- }
-
- //
- // Get rid of the old backing store object now that it is no
- // longer needed for anything
- //
- delete previousBackingStore;
- } // TDatabaseDocument::SetBackingStore
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::SaveACopy
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::SaveACopy(TAbstractBackingStore* backingStore)
- {
- //
- // If we already have a backing store, make sure that
- // the file it is attached to is saved before we
- // save a copy of it.
- //
- this->FlushChangesToDisk();
-
- //
- // Begin by writing out the document information into the new
- // backing store object.
- //
- this->WriteDocumentInformation(backingStore);
-
- //
- // There may be chunks of the database paged out to disk;
- // We must load them into memory and save them back to the
- // new backing store.
- //
- if(fRecordGroupList != nil)
- {
- for(long i=0; i<fNumberOfGroups; ++i)
- {
- //
- // If the record group is not in memory, then we
- // must read it in from the previous backing store
- // object.
- //
- if(fRecordGroupList[i] == nil)
- {
- TGroupControlObject* group = CreateGroupControlObject(i * kRecordsPerGroup);
- ASSERT(group == fRecordGroupList[i]);
- group->ReadRecordGroupFromDisk();
- }
- //
- // If we have the record group in memory, then write
- // it out to the new backing store object.
- //
- if(fRecordGroupList[i] != nil)
- {
- fRecordGroupList[i]->RecordGroupHasChanged();
- fRecordGroupList[i]->FlushChangesToDisk(backingStore);
- }
- }
- }
- } // TDatabaseDocument::SaveACopy
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::CanSaveDocument
- //--------------------------------------------------------------------------------
- Boolean TDatabaseDocument::CanSaveDocument()
- {
- Boolean canSave = false;
-
- if(fBackingStore != nil)
- canSave = fBackingStore->CanSaveDocument();
-
- return canSave;
- } // TDatabaseDocument::CanSaveDocument
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::DocumentName
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::DocumentName(TUpdataDataReference& name)
- {
- if(fBackingStore != nil)
- fBackingStore->DocumentName(name);
- else
- name.SetDataLength(0);
- }
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::DocumentNeedsSave
- //--------------------------------------------------------------------------------
- Boolean TDatabaseDocument::DocumentNeedsSave()
- {
- Boolean documentIsDirty = false;
-
- //
- // Test to see if any of the record groups need
- // to be saved.
- //
- if(fRecordGroupList != nil)
- for(long i=0; i<fNumberOfGroups; ++i)
- if(fRecordGroupList[i] != nil)
- {
- if(fRecordGroupList[i]->RecordGroupNeedsSave())
- {
- documentIsDirty = true;
- break;
- }
- }
-
- return documentIsDirty;
- } // TDatabaseDocument::DocumentNeedsSave
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::MakeRecord
- //
- // This routine should be called directly (after GetFreeRecord returns the index
- // of a record to use) to create a new database record. After the new record
- // is created, the creator should immediately set up the flags longword of the
- // new record.
- //
- // This routine is also called from TGroupControlObject::GetRecordCursor.
- //--------------------------------------------------------------------------------
- TAbstractRecord* TDatabaseDocument::MakeRecord(long recordIndex, long recordIDWord)
- {
- TGroupControlObject* groupObject = this->GetGroupControlObject(recordIndex);
- TAbstractRecord* cursor = nil;
-
- //
- // First test: if the high two bits are clear, this is a
- // block-data record
- //
- if((recordIDWord & kBalanceFactorBits) == 0)
- {
- cursor = new TDataRecord(this, recordIndex);
- }
- else
- {
- //
- // If the object record bit is clear, then this is an object record
- //
- if((recordIDWord & kObjectRecordDefinitionBit) == 0)
- {
- cursor = new TDBElement(this, recordIndex);
- }
- //
- // If the property record bit is clear, then this is a property record
- //
- else if((recordIDWord & kDataRecordDefinitionBit) == 0)
- {
- cursor = new TDBProperty(this, recordIndex);
- }
- //
- // Reserved for future expansion
- //
- else
- {
- FailErr(eDataCorrupt);
- }
- }
-
- //
- // Cache the cursor if it was created
- //
- if(cursor != nil)
- groupObject->CacheCreatedCursor(recordIndex, cursor);
-
- return cursor;
- } // TDatabaseDocument::MakeRecord
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::GetNextFreeIndex
- //--------------------------------------------------------------------------------
- long TDatabaseDocument::GetNextFreeIndex(long afterWhichFreeIndex) const
- {
- TGroupControlObject* group = this->GetGroupControlObject(afterWhichFreeIndex);
- return group->NextFreeIndex(afterWhichFreeIndex);
- } // TDatabaseDocument::GetNextFreeIndex
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::GetPreviousFreeIndex
- //--------------------------------------------------------------------------------
- long TDatabaseDocument::GetPreviousFreeIndex(long beforeWhichIndex) const
- {
- TGroupControlObject* group = this->GetGroupControlObject(beforeWhichIndex);
- long freeList = group->FreeListToUse(beforeWhichIndex);
- //
- // The first free list does not maintain a previous link.
- // The previous link of the first item in any list may be invalid.
- //
- if((freeList == 0) || (this->GetFirstFreeIndex(freeList) == beforeWhichIndex))
- return kNilIndex;
- else
- return group->PreviousFreeIndex(beforeWhichIndex);
- } // TDatabaseDocument::GetPreviousFreeIndex
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::PopIndexFromFreeList
- //
- // To make a new record, call this routine to get an index of a record that
- // isn't used, then call MakeRecordCursor directly, passing in the index and
- // the longword that identifies the type of record to create.
- //--------------------------------------------------------------------------------
- long TDatabaseDocument::PopIndexFromFreeList(long whichFreeList)
- {
- //
- // Get the first free index; if there are no free records
- // left, then make some more.
- //
- long theFreeIndex = this->GetFirstFreeIndex(whichFreeList);
- if(theFreeIndex != kNilIndex)
- {
- //
- // Get the next free index in the linked list
- //
- long nextFreeIndex = this->GetNextFreeIndex(theFreeIndex);
- this->SetFreeIndex(whichFreeList, nextFreeIndex);
-
- //
- // Mark the free node as being unlinked from the free list
- //
- TGroupControlObject* group = this->GetGroupControlObject(theFreeIndex);
- group->WriteRecordWord(theFreeIndex, kFreeNodeLinkByte, kFreeRecordNotLinkedToTree);
- }
- else
- {
- //
- // If we make more free records, at least one of the
- // new records is never pushed onto the free list
- //
- theFreeIndex = this->MakeMoreFreeRecords(whichFreeList);
- }
-
- this->VerifyFreeLists();
-
- return theFreeIndex;
- } // TDatabaseDocument::PopIndexFromFreeList
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::PushFreeRecordOntoFreeList
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::PushFreeRecordOntoFreeList(long recordToFree)
- {
- TGroupControlObject* group = this->GetGroupControlObject(recordToFree);
- long whichFreeList = group->FreeListToUse(recordToFree);
- long recordToFreeAfterMerge = recordToFree;
-
- //
- // Test to see if 'recordToFree' really is a free node.
- // We don't enforce the lack of a cursor, though, as it may
- // be that a cursor that is commiting or discarding changes
- // has called this method to push the record back onto a free list.
- //
- Require(group->IndexIsFree(recordToFree));
-
- //
- // Try to merge free blocks together
- //
- // We must ignore free blocks whose link word is kFreeRecordNotLinkedToTree,
- // because these blocks are already claimed by a transaction. Also, note
- // that we adjust 'recordToFree' and 'whichFreeList' if any blocks are merged.
- //
- if(whichFreeList > 0)
- {
- long previousRecord = group->PreviousRecordIndex(recordToFree);
- long nextRecord = group->NextRecordIndex(recordToFree);
-
- //
- // If the previous record is free, merge it
- //
- if((previousRecord != kNilIndex) && group->IndexIsFreeAndOnFreeList(previousRecord))
- {
- whichFreeList = group->MergeFreeBlocks(previousRecord, recordToFree);
- recordToFreeAfterMerge = previousRecord;
- ASSERT(nextRecord == group->NextRecordIndex(recordToFreeAfterMerge));
- }
-
- //
- // If the next record is free, merge it
- //
- if((nextRecord != kNilIndex) && group->IndexIsFreeAndOnFreeList(nextRecord))
- {
- whichFreeList = group->MergeFreeBlocks(recordToFreeAfterMerge, nextRecord);
- }
- }
-
- long nextFreeNode = this->GetFirstFreeIndex(whichFreeList);
- group->WriteRecordWord(recordToFreeAfterMerge, kFreeNodeLinkByte, nextFreeNode);
- this->SetFreeIndex(whichFreeList, recordToFreeAfterMerge);
-
- //
- // Data records are stored in doubly-linked lists so they may
- // be removed from their free list when they are merged with
- // adjacent free blocks (DB records are never merged with
- // any other record).
- //
- if(whichFreeList > 0)
- {
- //
- // Note that although we clear the previous free node link
- // of a record when we push it on the stack, we do not bother
- // to clean up the previous free node link when objects are
- // popped off the stack
- //
- group->WriteRecordWord(recordToFreeAfterMerge, kPreviousFreeNodeLinkByte, kNilIndex);
- if(nextFreeNode > kNilIndex)
- {
- //
- // We know that any record in a free list does not belong
- // to any transaction, so we blythly write to it
- //
- TGroupControlObject* groupOfNextFreeNode = this->GetGroupControlObject(nextFreeNode);
- groupOfNextFreeNode->WriteRecordWord(nextFreeNode, kPreviousFreeNodeLinkByte, recordToFreeAfterMerge);
- }
- }
-
- //
- // Any cached cursor to this node is now useless
- //
- group->RecordCursorStale(recordToFree);
- this->VerifyFreeLists();
- } // TDatabaseDocument::PushFreeRecordOntoFreeList
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::RemoveFromFreeList
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::RemoveFromFreeList(long recordToRemove)
- {
- TGroupControlObject* group = this->GetGroupControlObject(recordToRemove);
-
- //
- // If the record isn't currently on a free list, don't try to remove it again!
- //
- if(group->IndexIsFreeAndOnFreeList(recordToRemove) == true)
- {
- long freeList = group->FreeListToUse(recordToRemove);
-
- //
- // The first free list is singly-linked; we do not support
- // removing items from it.
- //
- Require(freeList > 0);
-
- long previousFreeIndex = this->GetPreviousFreeIndex(recordToRemove);
- long nextFreeIndex = this->GetNextFreeIndex(recordToRemove);
-
- //
- // If there is a next free index AND there is a previous free index,
- // then fix up the previous link of the next index. We don't bother
- // to do this if there is no previous index because we always allow
- // the previous link of the first item in the free list to be garbage.
- //
- if((nextFreeIndex != kNilIndex) && (previousFreeIndex != kNilIndex))
- {
- TGroupControlObject* groupOfNextFreeNode = this->GetGroupControlObject(nextFreeIndex);
- groupOfNextFreeNode->WriteRecordWord(nextFreeIndex, kPreviousFreeNodeLinkByte, previousFreeIndex);
- }
-
- //
- // If there is no previous link, then we need to fix up
- // the head of the list pointer
- //
- if(previousFreeIndex == kNilIndex)
- {
- //
- // We have blind faith that popping an item from the
- // free list will do exactly what we want it to if
- // the first item in the free list points to the
- // record we'd like to remove
- //
- if(this->GetFirstFreeIndex(freeList) == recordToRemove)
- {
- this->PopIndexFromFreeList(freeList);
- ASSERT(this->GetFirstFreeIndex(freeList) == nextFreeIndex);
- }
- else
- ASSERT(false);
- }
- else
- {
- TGroupControlObject* groupOfPreviousFreeNode = this->GetGroupControlObject(previousFreeIndex);
- groupOfPreviousFreeNode->WriteRecordWord(previousFreeIndex, kFreeNodeLinkByte, nextFreeIndex);
- }
- }
-
- //
- // Finally, mark this node as being free, but not linked to
- // the tree
- //
- group->WriteRecordWord(recordToRemove, kFreeNodeLinkByte, kFreeRecordNotLinkedToTree);
- } // TDatabaseDocument::RemoveFromFreeList
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::VerifyFreeLists
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::VerifyFreeLists() const
- {
- for(long whichFreeList=0; whichFreeList<=kLastDataBlockFreeList; ++whichFreeList)
- {
- long freeIndex = this->GetFirstFreeIndex(whichFreeList);
- long previousFreeIndex = kNilIndex;
- while(freeIndex != kNilIndex)
- {
- long nextFreeIndex = kNilIndex;
-
- TGroupControlObject* group = this->GetGroupControlObject(freeIndex);
- if(group->IndexIsFree(freeIndex) == false)
- DebugStr("\pNon-free record on the free list!");
- if(group->FreeListToUse(freeIndex) != whichFreeList)
- DebugStr("\pFree record is wrong size for its free list!");
- else
- {
- //
- // All free lists but the first maintain previous
- // pointers for a doubly-linked list. The first
- // item in the list may have a bogus previous link,
- // because we do not fix it up when we pop items.
- //
- if((whichFreeList > 0) && (previousFreeIndex != kNilIndex))
- {
- if(this->GetPreviousFreeIndex(freeIndex) != previousFreeIndex)
- DebugStr("\pPrevious free index link in free list doesn't point back");
- }
-
- nextFreeIndex = this->GetNextFreeIndex(freeIndex);
- }
-
- previousFreeIndex = freeIndex;
- freeIndex = nextFreeIndex;
- }
- }
- } // TDatabaseDocument::VerifyFreeLists
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::NewDBProperty
- //--------------------------------------------------------------------------------
- AnUpdate<TDBProperty> TDatabaseDocument::NewDBProperty(TTransaction* transaction)
- {
- AnUpdate<TDBProperty> dataUpdatePointer;
- REQUIREVALIDPOINTER(transaction);
- OSErr err = noErr;
-
- long freeIndex = this->PopIndexFromFreeList(kFreeRecordListIndex);
- Try
- {
- AConst<TDBProperty> dataCursor(this->MakeRecord(freeIndex, kInitialDataRecordFlags)->DBPropertyCursor());
- dataUpdatePointer = transaction->GetDBPropertyUpdatePointer(dataCursor);
- dataUpdatePointer->InitializeNewRecord(transaction);
- }
- Catch(err)
- {
- //
- // Push free index back onto a free list
- //
- Throw(err);
- }
-
- return dataUpdatePointer;
- } // TDatabaseDocument::NewDBProperty
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::NewDBElement
- //--------------------------------------------------------------------------------
- AnUpdate<TDBElement> TDatabaseDocument::NewDBElement(TTransaction* transaction)
- {
- AnUpdate<TDBElement> elementUpdatePtr;
- REQUIREVALIDPOINTER(transaction);
- OSErr err = noErr;
-
- long freeIndex = this->PopIndexFromFreeList(kFreeRecordListIndex);
- Try
- {
- AConst<TDBElement> objectCursor(this->MakeRecord(freeIndex, kInitialObjectRecordFlags)->DBElementCursor());
- elementUpdatePtr = transaction->GetDBElementUpdatePointer(objectCursor);
- elementUpdatePtr->InitializeNewRecord(transaction);
- }
- Catch(err)
- {
- //
- // Push free index back onto a free list
- //
- Throw(err);
- }
-
- return elementUpdatePtr;
- } // TDatabaseDocument::NewDBElement
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::NewDataRecord
- //--------------------------------------------------------------------------------
- AnUpdate<TDataRecord> TDatabaseDocument::NewDataRecord(TTransaction* transaction, long sizeOfData)
- {
- AnUpdate<TDataRecord> dataUpdatePtr;
- REQUIREVALIDPOINTER(transaction);
- OSErr err = noErr;
-
- //
- // Pick a free list that's the correct size for the amount of
- // data requested. Don't forget about the block header, either.
- //
- long sizeOfBlock = sizeOfData + kHeaderSize;
- long whichFreeList = (sizeOfBlock - 1) / (kSingleRecordSize);
- Require(whichFreeList <= kLastDataBlockFreeList);
- long freeIndex = this->PopIndexFromFreeList(whichFreeList);
- Try
- {
- AConst<TDataRecord> dataCursor(this->MakeRecord(freeIndex, kInitialDataBlockFlagsWord)->DataCursor());
- dataUpdatePtr = transaction->GetDataRecordUpdatePointer(dataCursor);
- dataUpdatePtr->InitializeNewDataRecord(transaction);
- }
- Catch(err)
- {
- //
- // Push free index back onto a free list
- //
- Throw(err);
- }
-
- return dataUpdatePtr;
- } // TDatabaseDocument::NewDataRecord
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::GetGroupControlObject
- //--------------------------------------------------------------------------------
- TGroupControlObject* TDatabaseDocument::GetGroupControlObject(long recordIndex) const
- {
- long whichGroupControlObject = recordIndex / kRecordsPerGroup;
-
- if((whichGroupControlObject < 0) || (whichGroupControlObject >= fNumberOfGroups))
- Throw(eIndexOutOfRange);
-
- //
- // The group control object may be purged or unloaded,
- // in which case fRecordGroupList[whichGroupControlObject]
- // will be nil. We need to test for this situation and
- // recreate the group control object and reload its data
- // from disk.
- //
- if(fRecordGroupList[whichGroupControlObject] == nil)
- {
- long firstRecord = whichGroupControlObject * kRecordsPerGroup;
- ((TDatabaseDocument*)this)->CreateGroupControlObject(firstRecord);
- }
- Require(fRecordGroupList[whichGroupControlObject] != nil);
-
- return fRecordGroupList[whichGroupControlObject];
- } // TDatabaseDocument::GetGroupControlObject
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::InitializeNewGroup
- //--------------------------------------------------------------------------------
- long TDatabaseDocument::InitializeNewGroup(TGroupControlObject* group)
- {
- REQUIREVALIDPOINTER(group);
- long newFreeNode = kNilIndex;
-
- long newFirstFreeNode = group->InitializeNewGroup(this->GetFirstFreeIndex(kFreeRecordListIndex), newFreeNode);
- if(newFirstFreeNode > kNilIndex)
- this->SetFreeIndex(kFreeRecordListIndex, newFirstFreeNode);
-
- return newFreeNode;
- } // TDatabaseDocument::InitializeNewGroup
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::InitializeNewDataGroup
- //--------------------------------------------------------------------------------
- long TDatabaseDocument::InitializeNewDataGroup(TGroupControlObject* group, long desiredEncodedPhysicalSize)
- {
- REQUIREVALIDPOINTER(group);
- long leftOverBlock = kNilIndex;
-
- long newFreeNode = group->InitializeNewDataGroup(desiredEncodedPhysicalSize, leftOverBlock);
- if(leftOverBlock > kNilIndex)
- this->PushFreeRecordOntoFreeList(leftOverBlock);
-
- return newFreeNode;
- } // TDatabaseDocument::InitializeNewDataGroup
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::CreateGroupList
- //
- // Make a list of pointers to group control objects
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::CreateGroupList(long numberOfGroups)
- {
- if(numberOfGroups > fNumberOfGroups)
- {
- //
- // Make a new list of group control objects,
- // copy the old list to the new list and delete
- // the old.
- //
- TGroupControlObject** newGroupList = new TGroupControlObject*[numberOfGroups];
- FailNil(newGroupList);
-
- if(fRecordGroupList != nil)
- CopyMemory(fRecordGroupList, newGroupList, fNumberOfGroups * sizeof(Ptr)); // memcpy(newGroupList, fRecordGroupList, fNumberOfGroups * sizeof(Ptr));
-
- delete [] fRecordGroupList;
- fRecordGroupList = newGroupList;
-
- //
- // Nil out the newly created pointers
- //
- while(numberOfGroups > fNumberOfGroups)
- {
- fRecordGroupList[fNumberOfGroups] = nil;
- ++fNumberOfGroups;
- }
- }
- }
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::CacheGroupControlObject
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::CacheGroupControlObject(TGroupControlObject* newGroup)
- {
- REQUIREVALIDPOINTER(newGroup);
- long newGroupNumber = newGroup->FirstRecordIndex() / kRecordsPerGroup;
- OSErr err = noErr;
-
- //
- // If there aren't enough pointers to group control
- // objects, then make more.
- //
- this->CreateGroupList(newGroupNumber + 1);
- fRecordGroupList[newGroupNumber] = newGroup;
- } // TDatabaseDocument::CacheGroupControlObject
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::CreateGroupControlObject
- //
- // Must not read any info from the disk; SetBackingStore depends on that.
- //--------------------------------------------------------------------------------
- TGroupControlObject* TDatabaseDocument::CreateGroupControlObject(long firstRecord)
- {
- TGroupControlObject* newGroup = nil;
-
- newGroup = new TGroupControlObject(this, firstRecord);
- FailNil(newGroup);
- this->CacheGroupControlObject(newGroup);
-
- return newGroup;
- } // TDatabaseDocument::CreateGroupControlObject
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::MakeMoreFreeRecords
- //--------------------------------------------------------------------------------
- long TDatabaseDocument::MakeMoreFreeRecords(long addToWhichFreeList)
- {
- TGroupControlObject* newGroup = nil;
- long newFreeNode = kNilIndex;
-
- //
- // If we need more room in the free record list,
- // then make a new set of records in a new record
- // group control object.
- //
- long firstNewRecord = fNumberOfGroups * kRecordsPerGroup;
- if(addToWhichFreeList == kFreeRecordListIndex)
- {
- newGroup = new TGroupControlObject(this, firstNewRecord);
- this->CacheGroupControlObject(newGroup);
- newFreeNode = this->InitializeNewGroup(newGroup);
- }
- else
- {
- //
- // Never make the data record left over from the
- // split only 1 block in size, because the rules
- // are different for single-block records (their
- // free list is singly-linked instead of doubly-linked)
- //
- long freeListToSplitFrom = addToWhichFreeList + 2;
-
- //
- // Look for the next larger free list that has
- // something in it
- //
- while(freeListToSplitFrom <= kLastDataBlockFreeList)
- {
- if(this->GetFirstFreeIndex(freeListToSplitFrom) != kNilIndex)
- break;
-
- ++freeListToSplitFrom;
- }
-
- //
- // If we went past the end of the list of free lists,
- // then split from the last free list in the set
- //
- if(freeListToSplitFrom > kLastDataBlockFreeList)
- freeListToSplitFrom = kLastDataBlockFreeList;
-
- //
- // If the list we decided to split from is empty,
- // then make a new group control object with
- // another data block in it.
- //
- if(this->GetFirstFreeIndex(freeListToSplitFrom) == kNilIndex)
- {
- newGroup = new TGroupControlObject(this, firstNewRecord);
- this->CacheGroupControlObject(newGroup);
- newFreeNode = this->InitializeNewDataGroup(newGroup, addToWhichFreeList);
- }
- else
- {
- newFreeNode = this->PopIndexFromFreeList(freeListToSplitFrom);
- TGroupControlObject* group = GetGroupControlObject(newFreeNode);
- long leftOverBlock = group->TrimBlock(newFreeNode, addToWhichFreeList);
- if(leftOverBlock > kNilIndex)
- this->PushFreeRecordOntoFreeList(leftOverBlock);
- }
- }
-
- return newFreeNode;
- } // TDatabaseDocument::MakeMoreFreeRecords
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::WriteDocumentInformation
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::WriteDocumentInformation(TAbstractBackingStore* backingStoreToUse /*= nil*/)
- {
- fDocumentInformation.fNumberOfGroupsOnDisk = fNumberOfGroups;
- if(backingStoreToUse == nil)
- backingStoreToUse = fBackingStore;
-
- if(backingStoreToUse != nil)
- {
- backingStoreToUse->Write(&this->fDocumentInformation, 0, sizeof(TDocumentFileInformation));
- backingStoreToUse->Write(&this->fFreeList, this->fDocumentInformation.fStartOfFreeList, kSizeOfFreeList);
- }
- }
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::ReadDocumentInformation
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::ReadDocumentInformation()
- {
- if(fBackingStore != nil)
- {
- //
- // Trash the format identifier, just to make sure that
- // 'Read' really is reading something.
- //
- this->fDocumentInformation.fFormatIdentifier = 'xxxx';
-
- //
- // Read in the document header
- //
- fBackingStore->Read(&this->fDocumentInformation, 0, sizeof(TDocumentFileInformation));
-
- //
- // Tests of logical consistancy:
- //
- // The format identifier must be correct
- //
- // The file format revision used to write this file must always be
- // greater than or equal to the "compatable" versions; otherwise, the
- // implication is that the file revision used to write this file
- // cannot be read by the version of the app that wrote it.
- //
- // The earliest write-compatible revision must always be greater than
- // or equal to the earliest compatible revision; otherwise the implication
- // is that some version of this app can write a file format that it
- // cannot read.
- //
- // Every valid saved document must have a meta-root.
- //
- Require(this->fDocumentInformation.fFormatIdentifier == kFormatIdentifier);
- Require(this->fDocumentInformation.fFileFormatRevisionNumber >= fDocumentInformation.fEarliestWriteCompatibleRevision);
- Require(this->fDocumentInformation.fEarliestWriteCompatibleRevision >= fDocumentInformation.fEarliestCompatibleRevisionNumber);
- Require(this->fDocumentInformation.fEarliestWriteCompatibleRevision >= fDocumentInformation.fEarliestCompatibleRevisionNumber);
- Require(this->fDocumentInformation.fMetaRootID != kNilIndex);
-
- //
- // Can we understand this file format?
- //
- if(this->fDocumentInformation.fEarliestCompatibleRevisionNumber > kFileFormatRevisionNumber)
- FailErr(-1); // •••Document is too new for us to read
- if(this->fDocumentInformation.fFileFormatRevisionNumber < kEarliestInterpretedRevisionNumber)
- FailErr(-1); // •••Document is too old for us to convert
-
- //
- // If this document was written with a more recent format, and
- // we are not allowed to write over said format, then mark the
- // document as read-only. If we are allowed to write over
- // this revision, then immediately convert the file format revision
- // numbers back down to what we will write out.
- //
- if(this->fDocumentInformation.fFileFormatRevisionNumber > kFileFormatRevisionNumber)
- {
- //
- // The document will tell us if it wants us to write over anything it may have
- // written that we don't know about; usually, 'fEarliestWriteCompatibleRevision'
- // will equal the version that the document was written out to disk with.
- //
- if(this->fDocumentInformation.fEarliestWriteCompatibleRevision > kFileFormatRevisionNumber)
- {
- fBackingStore->SetWriteEnable(false);
- }
- //
- // This branch should execute only rarely, if a new version of db+ introduces extensions
- // to the file format but does not mind if old versions of db+ ignore the extensions and
- // write garbage over them. If we're going to be in a position to do that, then we'll
- // reset our document file format revision numbers so that the newer versions of db+
- // don't think that the extened information is still there.
- //
- else
- {
- this->fDocumentInformation.fFileFormatRevisionNumber = kFileFormatRevisionNumber;
- this->fDocumentInformation.fEarliestCompatibleRevisionNumber = kEarliestCompatibleRevisionNumber;
- this->fDocumentInformation.fEarliestWriteCompatibleRevision = kEarliestWriteCompatibleRevision;
- this->fDocumentInformation.fNumberOfHeaderBytes = sizeof(TDocumentFileInformation);
- }
- }
-
- //
- // Read in the free list
- //
- fBackingStore->Read(&this->fFreeList, this->fDocumentInformation.fStartOfFreeList, kSizeOfFreeList);
- }
- this->CreateGroupList(fDocumentInformation.fNumberOfGroupsOnDisk);
-
- #if 0
- //
- // If this file format is older than the current format, then
- // convert it into the current format.
- //
- if(this->fDocumentInformation.fFileFormatRevisionNumber < kFileFormatRevisionNumber)
- {
- //
- // If there are bits in the header that were meaningless in the old format,
- // then zero them out here.
- //
- if(this->fDocumentInformation.fNumberOfHeaderBytes < sizeof(TDocumentFileInformation))
- {
- long bytesToClear = sizeof(TDocumentFileInformation) - this->fDocumentInformation.fNumberOfHeaderBytes;
- char* bogusData = ((char*)&this->fDocumentInformation.fDocumentID) + this->fDocumentInformation.fNumberOfHeaderBytes;
- while(bytesToClear--)
- *bogusData++ = 0;
- }
-
- //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- // BEGIN DOCUMENT CONVERSION CODE
- //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
- //
- // Right now we only have one file format, but if in the future we need to
- // convert from one format to another, then the code to do
- //
-
- //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- // END DOCUMENT CONVERSION CODE
- //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
- //
- // Once we have converted the format, write in updated version numbers
- //
- this->fDocumentInformation.fFileFormatRevisionNumber = kFileFormatRevisionNumber;
- this->fDocumentInformation.fEarliestCompatibleRevisionNumber = kEarliestCompatibleRevisionNumber;
- this->fDocumentInformation.fEarliestWriteCompatibleRevision = kEarliestWriteCompatibleRevision;
- this->fDocumentInformation.fNumberOfHeaderBytes = sizeof(TDocumentFileInformation);
- }
- #endif
- }
-
- //--------------------------------------------------------------------------------
- // TDatabaseDocument::CreateMetaRoot
- //--------------------------------------------------------------------------------
- void TDatabaseDocument::CreateMetaRoot()
- {
- TTransaction transaction;
-
- AnUpdate<TDBElement> metaRoot = this->NewDBElement(&transaction);
- fDocumentInformation.fMetaRootID = metaRoot->RecordIndex();
- transaction.CommitChanges();
- } // TDatabaseDocument::CreateMetaRoot
-
- //--------------------------------------------------------------------------------
- // TAbstractBackingStore::~TAbstractBackingStore
- //--------------------------------------------------------------------------------
- TAbstractBackingStore::~TAbstractBackingStore()
- {
- } // TAbstractBackingStore::~TAbstractBackingStore
-
- //--------------------------------------------------------------------------------
- // TAbstractBackingStore::CanSaveDocument
- //--------------------------------------------------------------------------------
- Boolean TAbstractBackingStore::CanSaveDocument()
- {
- return fCanWrite;
- }
-
-